#include "viewerpriv.h"
#include "options.h"
#include "viewer.options.h"
ViewerPriv::ViewerPriv(Session* s, Viewer* v) :
	session{ s },
	viewer{ v }, viewerShared{ Tracer::instance()->viewerShared() },
	graph{},
	cube_textures{ 0, },
	max_dimension_size{ 0.0 },
	colors{}, colorMode{ false },
	up{ 0, -1.0f, 0 }, rgt{ 0, 0, -1.0f }, radU{ -1 },
	curPos{}, tgtPos{},
	pickA{}, pickB{},
	mView{}, mrView{}, mProj{}, mrProj{},
	scale_factor{ 0 }, fbo_width{ 0 }, fbo_height{ 0 },
	funcs{ nullptr },
	pathBuffers{}, vao_fixed{ 0 },
	vao_progr{ 0 }, vbo_progr{ 0 }, num_progr{ 0 },
	fbo_scale{ 0 }, fbotex_scale{ 0, },
	fbo_opaque{ 0 }, fbotex_opaque{ 0, },
	fbo_surface{ 0 }, fbotex_surface{ 0, },
	fbo_edges{ 0 }, fbotex_edges{ 0 },
	fbo_cubes{ 0, }, fbotex_cubes{ 0, },
	pbiPath{ 0 }, num_path{ 0 },
	mouse{}, slice_num{ 800 }, slice_shown{ 800 }, slice_delta{ 0 },
	slice_mode{ false }, show_edges{ true }
{
	PathBuffer pbHead;
	pbHead.vao = 0;
	pbHead.vbo = 0;
	pbHead.prev_unused = 0;
	pathBuffers.emplace_back(pbHead);

	// XXX color config
	for (int i = 0; i<COLOR_NUM; i++) {
		//
		colors[i] = colorData[i].def;
	}
	timer = new QTimer{ viewer };
	timer->setInterval(33);
	QObject::connect(timer, &QTimer::timeout, [this]() { this->rotationTimeout(); });
}

ViewerPriv::~ViewerPriv() {
	if (funcs) {
		gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, 0);
		gl(BindVertexArray, funcs)(0);
		for (size_t i = 1; i<pathBuffers.size(); i++) {
			gl(DeleteBuffers, funcs)(1, &pathBuffers[i].vbo);
			gl(DeleteVertexArrays, funcs)(1, &pathBuffers[i].vao);
		}
		gl(DeleteBuffers, funcs)(1, &vbo_progr);
		gl(DeleteVertexArrays, funcs)(1, &vao_fixed);
		gl(DeleteVertexArrays, funcs)(1, &vao_progr);

		gl(DeleteBuffers, funcs)(2, vbo_mesh);
		gl(DeleteVertexArrays, funcs)(1, &vao_mesh);

		gl(ActiveTexture, funcs)(GL_TEXTURE0);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, 0);
		gl(DeleteTextures, funcs)(2, fbotex_scale);

		for (int i = 0; i<6; i++) {
			gl(ActiveTexture, funcs)(GL_TEXTURE2 + i);
			gl(BindTexture, funcs)(GL_TEXTURE_2D, 0);
		}
		gl(DeleteTextures, funcs)(6, fbotex_cubes);

		for (int i = 0; i<4; i++) {
			gl(ActiveTexture, funcs)(GL_TEXTURE8 + i);
			gl(BindTexture, funcs)(GL_TEXTURE_2D, 0);
		}
		gl(DeleteTextures, funcs)(4, fbotex_edges);

		for (int i = 0; i<2; i++) {
			gl(ActiveTexture, funcs)(GL_TEXTURE12 + i);
			gl(BindTexture, funcs)(GL_TEXTURE_2D, 0);
		}
		gl(DeleteTextures, funcs)(2, fbotex_opaque);

		for (int i = 0; i<2; i++) {
			gl(ActiveTexture, funcs)(GL_TEXTURE14 + i);
			gl(BindTexture, funcs)(GL_TEXTURE_2D, 0);
		}
		gl(DeleteTextures, funcs)(2, fbotex_surface);
		gl(DeleteTextures, funcs)(1, &textTexture);

		gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, 0);
		gl(DeleteFramebuffers, funcs)(1, &fbo_scale);
		gl(DeleteFramebuffers, funcs)(1, &fbo_opaque);
		gl(DeleteFramebuffers, funcs)(1, &fbo_surface);
		gl(DeleteFramebuffers, funcs)(1, &fbo_edges);
		gl(DeleteFramebuffers, funcs)(3, fbo_cubes);

		viewerShared->deinitialize(funcs);
	}
}
void ViewerPriv::updateVoxelSizes(int i) {
	min_voxel_sizes[i] = qInf();
	for (int j = 0; j<3; j++) {
		if (min_voxel_sizes[i]>cubes[i].xform.resolution[j])
			min_voxel_sizes[i] = cubes[i].xform.resolution[j];
		//if(min_voxel_size>cubes[i].xform.resolution[j])
		//min_voxel_size=cubes[i].xform.resolution[j];
	}
}
bool ViewerPriv::setSession(Options* options, Graph gr, const std::array<Cube, 9>& _cubes, const std::array<std::pair<double, double>, 9>& _xfuncs, const std::array<double, 6>& bbox, double mvol) {
	graph = gr;
	cubes = _cubes;
	xfuncs = _xfuncs;

	max_dimension_size = 0.0;
	if (bbox[1] - bbox[0]>max_dimension_size)
		max_dimension_size = bbox[1] - bbox[0];
	if (bbox[3] - bbox[2]>max_dimension_size)
		max_dimension_size = bbox[3] - bbox[2];
	if (bbox[5] - bbox[4]>max_dimension_size)
		max_dimension_size = bbox[5] - bbox[4];

	min_voxel_size = mvol;
	for (int i = 0; i<9; i++) {
		if (cubes[i].data) {
			updateCubeMatrix(i);
			updateVoxelSizes(i);
		}
	}

	double x, y, z;
	if (options->getXyz("viewer.direction.up", &x, &y, &z)) {
		up.setX(x); up.setY(y); up.setZ(z);
	}
	else {
		up.setX(0); up.setY(-1); up.setZ(0);
	}
	if (options->getXyz("viewer.direction.right", &x, &y, &z)) {
		rgt.setX(x); rgt.setY(y); rgt.setZ(z);
	}
	else {
		rgt.setX(0); rgt.setY(0); rgt.setZ(-1);
	}
	if (options->getDouble("viewer.zoom", &x)) {
		radU = x;
	}
	else {
		radU = max_dimension_size / 2;
	}
	

	ViewerGeneralOptions::instance()->config(this, options);
	ViewerColorOptions::instance()->config(this, options);
	ViewerVolumeOptions::instance()->config(this, options);
	auto dpr = viewer->devicePixelRatio();
	auto w = viewer->width();
	auto h = viewer->height();
	int ww = dpr*w;
	int hh = dpr*h;

	viewer->makeCurrent();
	resizeFrameBuffers(ww, hh);
	initGraphPbi();
	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, pathBuffers[pbiFixed].vbo);
	auto ptr = static_cast<PointGL*>(funcs->glMapBufferRange(GL_ARRAY_BUFFER, (2) * sizeof(PointGL), 9 * 24 * sizeof(PointGL), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT));
	for (int ci = 0; ci<9; ci++) {
		if (cubes[ci].data) {
			setAxes(cubes[ci], &ptr[ci * 24]);
		}
	}
	gl(UnmapBuffer, funcs)(GL_ARRAY_BUFFER);
	viewer->doneCurrent();

	viewer->setCursor(Qt::CrossCursor);
	return true;
}
bool ViewerPriv::updateBoundingBox(const std::array<double, 6>& bbox) {
	if (bbox[1] - bbox[0]>max_dimension_size)
		max_dimension_size = bbox[1] - bbox[0];
	if (bbox[3] - bbox[2]>max_dimension_size)
		max_dimension_size = bbox[3] - bbox[2];
	if (bbox[5] - bbox[4]>max_dimension_size)
		max_dimension_size = bbox[5] - bbox[4];
	return true;
}
void ViewerPriv::setAxes(const Cube& c, PointGL* ptr) {
	int64_t size[3] = { c.data.width(), c.data.height(), c.data.depth() };
	int32_t p0[3] = { c.data.cubeId().x0, c.data.cubeId().y0, c.data.cubeId().z0 };
	for (int i = 0; i<3; i++) {
		printMessage(size[i]);
	}
	for (int dir = 0; dir<3; dir++) {
		for (int i = 0; i<2; i++) {
			for (int j = 0; j<2; j++) {
				for (int k = 0; k<2; k++) {
					double pp[3];
					for (int l = 0; l<3; l++)
						pp[l] = c.xform.origin[l]
						+ c.xform.direction[l + dir * 3] * (k*size[dir] + p0[dir])
						+ c.xform.direction[l + (dir + 1) % 3 * 3] * (j*size[(dir + 1) % 3] + p0[(dir + 1) % 3])
						+ c.xform.direction[l + (dir + 2) % 3 * 3] * (i*size[(dir + 2) % 3] + p0[(dir + 2) % 3]);
					Point p(pp[0], pp[1], pp[2]);
					ptr[dir * 8 + i * 4 + j * 2 + k].p[0] = p._x;
					ptr[dir * 8 + i * 4 + j * 2 + k].p[1] = p._y;
					ptr[dir * 8 + i * 4 + j * 2 + k].p[2] = p._z;
				}
			}
		}
	}
}
void ViewerPriv::updateXfunc(int ch, const std::pair<double, double>& xfunc) {
	xfuncs[ch] = xfunc;
}

bool ViewerPriv::pickPoint(int x, int y) {
	int pick_size;
	int fbx, fby;
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		pick_size = PICK_SIZE*(1 - scale_factorTemp);
		fbx = x*(1 - scale_factorTemp) + (1 - scale_factorTemp) / 2;
		fby = y*(1 - scale_factorTemp) + (1 - scale_factorTemp) / 2;
	}
	else {
		pick_size = PICK_SIZE;
		fbx = x / (1 + scale_factorTemp);
		fby = y / (1 + scale_factorTemp);
	}
	//

	float px = (fbx * 2 + 1.0) / fbo_width - 1;
	float py = 1 - (fby * 2 + 1.0) / fbo_height;
	if (slice_mode) {
		float depth = -1.0*slice_delta / slice_num;
		QVector3D lp = mrView*mrProj*QVector3D(px, py, depth);
		Point p = toGlobal(lp);
		session->pickPosition(Edge{}, 0, &p);
		return true;
	}

	bool ret = false;
	viewer->makeCurrent();
	if (fbx - pick_size >= 0 && fby - pick_size >= 0
		&& fby + pick_size<fbo_height
		&& fbx + pick_size<fbo_width) {
		int pickdr2 = pick_size*pick_size + 1;
		int pickI = -1;
		std::vector<GLuint> bufIdx((2 * pick_size + 1)*(2 * pick_size + 1));
		gl(BindFramebuffer, funcs)(GL_READ_FRAMEBUFFER, fbo_edges);
		gl(ReadBuffer, funcs)(GL_COLOR_ATTACHMENT0);
		gl(ReadPixels, funcs)(fbx - pick_size, fbo_height - 1 - fby - pick_size, pick_size * 2 + 1, pick_size * 2 + 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &bufIdx[0]);

		for (int dy = -pick_size; dy <= pick_size; dy++) {
			for (int dx = -pick_size; dx <= pick_size; dx++) {
				int i = dx + pick_size + (dy + pick_size)*(2 * pick_size + 1);
				if (bufIdx[i]>0) {
					auto dr2 = dx*dx + dy*dy;
					if (dr2<pickdr2) {
						pickI = i;
						pickdr2 = dr2;
					}
				}
			}
		}
		//
		//
		//

		size_t pickIdx = 0;

		if (pickI >= 0) {
			pickIdx = bufIdx[pickI];
			gl(ReadBuffer, funcs)(GL_COLOR_ATTACHMENT1);
			std::vector<GLint> bufPos((2 * pick_size + 1)*(2 * pick_size + 1));
			gl(ReadPixels, funcs)(fbx - pick_size, fbo_height - 1 - fby - pick_size, pick_size * 2 + 1, pick_size * 2 + 1, GL_RED_INTEGER, GL_INT, &bufPos[0]);
			viewer->doneCurrent();

			auto e = graph.edges()[pickIdx - 1];
			size_t pickPos = bufPos[pickI];

			if (pickPos>e.points().size() - 1) pickPos = e.points().size() - 1;
			//fprintf(stderr, " pick %d [%d, %d]\n", pickPos, 0, e.points().size()-1);
			const auto& p = e.points()[pickPos];
			const auto& p0 = e.points()[0];
			const auto& p1 = e.points()[e.points().size() - 1];
			if (pickPos == 0) {
				session->pickPosition(e, 0, &p0);
				return true;
			}
			if (pickPos == e.points().size() - 1) {
				session->pickPosition(e, e.points().size() - 1, &p1);
				return true;
			}
			if (p.distTo(p0)<p0.r()) {
				session->pickPosition(e, 0, &p0);
				return true;
			}
			if (p.distTo(p1)<p1.r()) {
				session->pickPosition(e, e.points().size() - 1, &p1);
				return true;
			}
			session->pickPosition(e, pickPos, &p);
			return true;
		}

		pick_size /= 2;
		int mdx = 0, mdy = 0;
		GLuint maxv = 0;
		std::vector<GLuint> bufRed((2 * pick_size + 1)*(2 * pick_size + 1));
		gl(BindFramebuffer, funcs)(GL_READ_FRAMEBUFFER, fbo_cubes[0]);
		gl(ReadBuffer, funcs)(GL_COLOR_ATTACHMENT0);
		gl(ReadPixels, funcs)(fbx - pick_size, fbo_height - 1 - fby - pick_size, pick_size * 2 + 1, pick_size * 2 + 1, GL_RED, GL_UNSIGNED_INT, &bufRed[0]);
		for (int dy = -pick_size; dy <= pick_size; dy++) {
			for (int dx = -pick_size; dx <= pick_size; dx++) {
				int i = dx + pick_size + (dy + pick_size)*(2 * pick_size + 1);
				auto v = bufRed[i];
				//printMessage(dx, ' ', dy, ": ", v);
				if (v>maxv) {
					maxv = v;
					mdx = dx;
					mdy = dy;
				}
			}
		}
		if (maxv>0) {
			GLfloat mvaldepth;
			//gl(ReadBuffer, funcs)(GL_DEPTH_STENCIL_ATTACHMENT);
			gl(ReadPixels, funcs)(fbx + mdx, fbo_height - 1 - fby + mdy, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &mvaldepth);
			//viewer->doneCurrent();
			auto xx = mdx + pick_size + fbx - pick_size;
			auto yy = -mdy - pick_size + fby + pick_size;
			QVector3D pp((xx * 2 + 1.0) / fbo_width - 1, 1 - (yy * 2 + 1.0) / fbo_height, mvaldepth * 2 - 1);
			pp = mrView*mrProj*pp;
			auto p = toGlobal(pp);
			QString text = "X: " + QString::number(p.x()) + " Y: " + QString::number(p.y()) + " Z: " + QString::number(p.z());
			viewer->paintText(text.toStdString(), 5,12);
			session->pickPosition(Edge{}, -1, &p);
			ret = true;
		}
	}

	QVector3D a(px, py, -1);
	QVector3D b(px, py, 1);
	a = mrView*mrProj*a;
	b = mrView*mrProj*b;
	QVector3D c = a - b;
	auto lenc = c.length();
	c = c / lenc;
	QVector3D selC = pickA - pickB;
	selC.normalize();
	QVector3D d = QVector3D::crossProduct(c, selC);

	QMatrix4x4 mat{};
	mat.setColumn(0, QVector4D(c, 0));
	mat.setColumn(1, QVector4D(-selC, 0));
	mat.setColumn(2, QVector4D(d, 0));
	QMatrix4x4 matr = mat.inverted();
	QVector3D s = matr*(pickB - b);
	//printMessage("pickpoint %1 %2 %3", d.length(), s.x(), lenc);
	if (d.length()<0.1 || s.x()<0 || s.x()>lenc) {
		pickA = a;
		pickB = b;
		gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, pathBuffers[pbiFixed].vbo);
		auto ptr = static_cast<PointGL*>(funcs->glMapBufferRange(GL_ARRAY_BUFFER, 0 * sizeof(PointGL), 2 * sizeof(PointGL), GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_RANGE_BIT));
		auto pa = toGlobal(pickA);
		auto pb = toGlobal(pickB);
		ptr[0].p[0] = pa._x;
		ptr[0].p[1] = pa._y;
		ptr[0].p[2] = pa._z;
		ptr[1].p[0] = pb._x;
		ptr[1].p[1] = pb._y;
		ptr[1].p[2] = pb._z;
		gl(UnmapBuffer, funcs)(GL_ARRAY_BUFFER);
		viewer->doneCurrent();
		return ret;
	}
	QVector3D t = b + s.x()*c;
	Point gt = toGlobal(t);
	pickA = pickB = QVector3D();
	session->pickPosition(Edge{}, 0, &gt);
	viewer->doneCurrent();
	return true;
}

QVector3D ViewerPriv::toLocal(const Point& p) const {
	Point d;
	d._x = p._x - curPos.point._x;
	d._y = p._y - curPos.point._y;
	d._z = p._z - curPos.point._z;
	return QVector3D(d.x(), d.y(), d.z());
}
Point ViewerPriv::toGlobal(const QVector3D& p) const {
	auto x = p.x() + curPos.point.x();
	auto y = p.y() + curPos.point.y();
	auto z = p.z() + curPos.point.z();
	return Point{ x, y, z };
}
void ViewerPriv::updateMVPMatrices() { // Depends on: *rgt, *radU, *up, *fbo_height, *fbo_width
	mView.setToIdentity();
	mView.lookAt(rgt*radU, QVector3D{ 0, 0, 0 }, up);
	mrView = mView.inverted();

	float vspan = radU*fbo_height / fbo_width;
	mProj.setToIdentity();
	mProj.ortho(-radU, radU, -vspan, vspan, 0.5*radU, 1.5*radU);
	rulerlength = 50.0 / vspan;
	mrProj = mProj.inverted();
}
void ViewerPriv::updateCubeMatrix(int ch) { // Depends on: *curPos, cube.xform, cube.data
	std::array<double, 3> p0{ curPos.point.x(), curPos.point.y(), curPos.point.z() };
	for (int i = 0; i<3; i++)
		p0[i] -= cubes[ch].xform.origin[i];
	std::array<int32_t, 3> p1{ -cubes[ch].data.cubeId().x0, -cubes[ch].data.cubeId().y0, -cubes[ch].data.cubeId().z0 };
	std::array<int64_t, 3> size{ cubes[ch].data.widthAdj(), cubes[ch].data.heightAdj(), cubes[ch].data.depthAdj() };
	for (int i = 0; i<4; i++)
		cube_matrices[ch](3, i) = i<3 ? 0 : 1;
	auto& rdir = cubes[ch].xform.direction_inv;
	for (int i = 0; i<3; i++) {
		double v = p1[i];
		for (int j = 0; j<3; j++) {
			cube_matrices[ch](i, j) = rdir[i + j * 3] / size[i];
			v += rdir[i + j * 3] * p0[j];
		}
		cube_matrices[ch](i, 3) = v / size[i];
	}
	for (int i = 0; i<4; i++) {
		for (int j = 0; j<4; j++) {
			printMessage(i, j, ": ", cube_matrices[ch](i, j));
		}
	}
}

void ViewerPriv::initializeGL() {
	funcs = viewer->context()->versionFunctions<QOpenGLFunctions_3_3_Core>();
	if (!funcs) {
		throwError("Failed to obtain OpenGL function pointers.");
		auto funcs = viewer->context()->functions();
		throwError("OpenGL API 3.3 not supported. (", funcs->glGetString(GL_VERSION), ")");
	}

	viewerShared->initialize(funcs);

	gl(GenTextures, funcs)(1, &textTexture);
	gl(BindTexture, funcs)(GL_TEXTURE_2D,textTexture);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, viewerShared->vbo_fixed);
	gl(GenVertexArrays, funcs)(1, &vao_fixed);
	gl(BindVertexArray, funcs)(vao_fixed);
	gl(EnableVertexAttribArray, funcs)(0);
	gl(VertexAttribPointer, funcs)(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 3, 0);

	gl(GenVertexArrays, funcs)(1, &vao_progr);
	gl(BindVertexArray, funcs)(vao_progr);
	gl(GenBuffers, funcs)(1, &vbo_progr);
	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, vbo_progr);
	gl(EnableVertexAttribArray, funcs)(0);
	gl(VertexAttribIPointer, funcs)(0, 3, GL_INT, sizeof(GLint) * 3, nullptr);

	auto dpr = viewer->devicePixelRatio();
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		fbo_width = viewer->width()*dpr*(1 - scale_factorTemp);
		fbo_height = viewer->height()*dpr*(1 - scale_factorTemp);
	}
	else {
		fbo_width = (viewer->width()*dpr + scale_factorTemp) / (1 + scale_factorTemp);
		fbo_height = (viewer->height()*dpr + scale_factorTemp) / (1 + scale_factorTemp);
	}

	gl(GenFramebuffers, funcs)(1, &fbo_opaque);
	gl(GenTextures, funcs)(2, fbotex_opaque);
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_opaque);
	gl(ActiveTexture, funcs)(GL_TEXTURE12);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_opaque[0]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fbotex_opaque[0], 0);
	gl(ActiveTexture, funcs)(GL_TEXTURE13);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_opaque[1]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbotex_opaque[1], 0);
	gl(DrawBuffer, funcs)(GL_COLOR_ATTACHMENT0);
	auto check_fbo = funcs->glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (check_fbo != GL_FRAMEBUFFER_COMPLETE) {
		throwError("Failed to create framebuffer fbo_opaque: %1", check_fbo);
	}

	gl(GenFramebuffers, funcs)(1, &fbo_surface);
	gl(GenTextures, funcs)(2, fbotex_surface);
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_surface);
	gl(ActiveTexture, funcs)(GL_TEXTURE14);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_surface[0]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fbotex_surface[0], 0);
	gl(ActiveTexture, funcs)(GL_TEXTURE15);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_surface[1]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbotex_surface[1], 0);
	gl(DrawBuffer, funcs)(GL_COLOR_ATTACHMENT0);
	check_fbo = funcs->glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (check_fbo != GL_FRAMEBUFFER_COMPLETE) {
		throwError("Failed to create framebuffer fbo_surface: %1", check_fbo);
	}

	gl(GenFramebuffers, funcs)(1, &fbo_edges);
	gl(GenTextures, funcs)(4, fbotex_edges);
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_edges);
	gl(ActiveTexture, funcs)(GL_TEXTURE8);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[0]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fbo_width, fbo_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, fbotex_edges[0], 0);
	gl(ActiveTexture, funcs)(GL_TEXTURE9);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[1]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_R32I, fbo_width, fbo_height, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbotex_edges[1], 0);
	gl(ActiveTexture, funcs)(GL_TEXTURE10);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[2]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_R32I, fbo_width, fbo_height, 0, GL_RED_INTEGER, GL_INT, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, fbotex_edges[2], 0);
	gl(ActiveTexture, funcs)(GL_TEXTURE11);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[3]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, fbotex_edges[3], 0);
	GLenum bufs[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2 };
	gl(DrawBuffers, funcs)(3, bufs);
	check_fbo = funcs->glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (check_fbo != GL_FRAMEBUFFER_COMPLETE) {
		throwError("Failed to create framebuffer fbo_edges %1", check_fbo);
	}

	gl(GenFramebuffers, funcs)(3, fbo_cubes);
	gl(GenTextures, funcs)(6, fbotex_cubes);
	for (int i = 0; i<3; i++) {
		gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_cubes[i]);
		gl(ActiveTexture, funcs)(GL_TEXTURE2 + i * 2 + 0);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_cubes[2 * i + 0]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
		gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fbotex_cubes[2 * i + 0], 0);
		gl(ActiveTexture, funcs)(GL_TEXTURE2 + i * 2 + 1);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_cubes[2 * i + 1]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
		gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		gl(TexParameteri, funcs)(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbotex_cubes[2 * i + 1], 0);
		gl(DrawBuffer, funcs)(GL_COLOR_ATTACHMENT0);
		check_fbo = funcs->glCheckFramebufferStatus(GL_FRAMEBUFFER);
		if (check_fbo != GL_FRAMEBUFFER_COMPLETE) {
			throwError("Failed to create framebuffer fbo_cubes: %1", check_fbo);
		}
	}

	gl(GenFramebuffers, funcs)(1, &fbo_scale);
	gl(GenTextures, funcs)(2, fbotex_scale);
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_scale);
	gl(ActiveTexture, funcs)(GL_TEXTURE0);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_scale[0]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, fbotex_scale[0], 0);
	gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_scale[1]);
	gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
	gl(FramebufferTexture2D, funcs)(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbotex_scale[1], 0);
	gl(DrawBuffer, funcs)(GL_COLOR_ATTACHMENT0);
	check_fbo = funcs->glCheckFramebufferStatus(GL_FRAMEBUFFER);
	if (check_fbo != GL_FRAMEBUFFER_COMPLETE) {
		throwError("Failed to create framebuffer fbo_scale: %1", check_fbo);
	}

	/*
	GLint v;
	gl(GetRenderbufferParameteriv, funcs)(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &v);
	fprintf(stderr, "w %d\n", v);
	gl(GetRenderbufferParameteriv, funcs)(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &v);
	fprintf(stderr, "h %d\n", v);
	gl(GetRenderbufferParameteriv, funcs)(GL_RENDERBUFFER, GL_RENDERBUFFER_INTERNAL_FORMAT, &v);
	fprintf(stderr, "f %d\n", v);
	gl(GetRenderbufferParameteriv, funcs)(GL_RENDERBUFFER, GL_RENDERBUFFER_SAMPLES, &v);
	fprintf(stderr, "s %d\n", v);
	gl(GetRenderbufferParameteriv, funcs)(GL_RENDERBUFFER, GL_RENDERBUFFER_RED_SIZE, &v);
	fprintf(stderr, "r %d\n", v);
	*/

	gl(ClearColor, funcs)(0.0, 0.0, 0.0, 1.0);
	gl(ClearDepth, funcs)(1.0);
	gl(Enable, funcs)(GL_DEPTH_TEST);
	gl(Enable, funcs)(GL_BLEND);
	gl(BlendFuncSeparate, funcs)(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

	pbiFixed = createPbi();
	pbiPath = createPbi();
	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, pathBuffers[pbiFixed].vbo);
	gl(BufferData, funcs)(GL_ARRAY_BUFFER, (9 * 24 + 2) * sizeof(PointGL), nullptr, GL_STATIC_DRAW);


#ifndef NDEBUG
	meshVerts.push_back(MeshVert{ 5000 * 256, 0, 0, 0.0, 0.0, 0.0 });
	meshVerts.push_back(MeshVert{ 0, 5000 * 256, 0, 0.0, 0.0, 0.0 });
	meshVerts.push_back(MeshVert{ 0, 0, 5000 * 256, 0.0, 0.0, 0.0 });
	meshIdxes.push_back(0);
	meshIdxes.push_back(2);
	meshIdxes.push_back(1);
#endif // DEBUG
	

	gl(GenVertexArrays, funcs)(1, &vao_mesh);
	gl(BindVertexArray, funcs)(vao_mesh);
	gl(GenBuffers, funcs)(2, vbo_mesh);
	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, vbo_mesh[0]);
	gl(EnableVertexAttribArray, funcs)(0);
	gl(VertexAttribIPointer, funcs)(0, 3, GL_INT, sizeof(MeshVert), 0);
	gl(EnableVertexAttribArray, funcs)(1);
	gl(VertexAttribPointer, funcs)(1, 3, GL_FLOAT, GL_FALSE, sizeof(MeshVert), static_cast<char*>(nullptr) + offsetof(MeshVert, nx));
	gl(BufferData, funcs)(GL_ARRAY_BUFFER, sizeof(MeshVert)*meshVerts.size(), &meshVerts[0], GL_STATIC_DRAW);

	gl(BindBuffer, funcs)(GL_ELEMENT_ARRAY_BUFFER, vbo_mesh[1]);
	gl(BufferData, funcs)(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*meshIdxes.size(), &meshIdxes[0], GL_STATIC_DRAW);
	//gl(EnableVertexAttribArray, funcs)(2);
	//gl(VertexAttribIPointer, funcs)(2, 1, GL_UNSIGNED_SHORT, sizeof(MeshVert), static_cast<char*>(nullptr)+offsetof(MeshVert, id));
	//gl(EnableVertexAttribArray, funcs)(3);
	//gl(VertexAttribIPointer, funcs)(3, 1, GL_UNSIGNED_SHORT, sizeof(MeshVert), static_cast<char*>(nullptr)+offsetof(MeshVert, par));
	//
}
std::vector<PointGL> ViewerPriv::pathToPathGL(const std::vector<Point>& points,const Point &leftPoint, const Point &rightPoint) {
	std::vector<PointGL> pathGL(points.size() + 1);
	//pathGL[0].p[0] = parentPoint._x;
	//pathGL[0].p[1] = parentPoint._y;
	//pathGL[0].p[2] = parentPoint._z;
	//pathGL[0].r = parentPoint._r;
	//pathGL[0].m = parentPoint.m;
	for (size_t i = 0; i<points.size(); i++) {
		auto& p = points[i];
		pathGL[i].p[0] = p._x;
		pathGL[i].p[1] = p._y;
		pathGL[i].p[2] = p._z;
		pathGL[i].r = p._r;
		pathGL[i].m = p.m;
		//std::cout << p._x << "\t" << p._y << "\t" << p._z << std::endl;
	}
	//std::cout <<"parentPoint"<< leftPoint._x << "\t" << leftPoint._y << "\t" << leftPoint._z << std::endl;
	//if (1) {
	//	pathGL[0].p[0] = leftPoint._x;
	//	pathGL[0].p[1] = leftPoint._y;
	//	pathGL[0].p[2] = leftPoint._z;
	//	pathGL[0].r = leftPoint._r;
	//	pathGL[0].m = leftPoint.m;

	//	pathGL[points.size()-1].p[0] = rightPoint._x;
	//	pathGL[points.size()-1].p[1] = rightPoint._y;
	//	pathGL[points.size()-1].p[2] = rightPoint._z;
	//	pathGL[points.size()-1].r = rightPoint._r;
	//	pathGL[points.size()-1].m = rightPoint.m;
	//}
	pathGL.back().p[0] = points.back()._x;
	pathGL.back().p[1] = points.back()._y;
	pathGL.back().p[2] = points.back()._z;
	pathGL.back().r = 0;
	pathGL.back().m = 0;
	return pathGL;
}
void ViewerPriv::initGraphPbi() {
	for (auto e : graph.edges()) {
		auto ep = EdgePriv::get(e);
		if (ep->vaoi <= 0) {
			auto lp = ep->leftVertex.point();
			auto rp = ep->rightVertex;
			bool back = false;
			if (rp == ep->parentVertex)
				back = true;
			auto pathGL = pathToPathGL(ep->points, ep->leftVertex.point(), ep->rightVertex.point());
			int vaoi = createPbi();
			gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, pathBuffers[vaoi].vbo);
			gl(BufferData, funcs)(GL_ARRAY_BUFFER, sizeof(PointGL)*pathGL.size(), pathGL.data(), GL_STATIC_DRAW);
			ep->vaoi = vaoi;
		}
	}
}
int ViewerPriv::createPbi() {
	if (pathBuffers[0].prev_unused) {
		auto i = pathBuffers[0].prev_unused;
		pathBuffers[0].prev_unused = pathBuffers[i].prev_unused;
		return i;
	}
	PathBuffer pbItem;
	gl(GenVertexArrays, funcs)(1, &pbItem.vao);
	gl(BindVertexArray, funcs)(pbItem.vao);
	gl(GenBuffers, funcs)(1, &pbItem.vbo);
	gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, pbItem.vbo);

	gl(EnableVertexAttribArray, funcs)(0);
	gl(VertexAttribIPointer, funcs)(0, 3, GL_INT, sizeof(PointGL), static_cast<int8_t*>(nullptr) + offsetof(PointGL, p));

	gl(EnableVertexAttribArray, funcs)(1);
	gl(VertexAttribIPointer, funcs)(1, 1, GL_UNSIGNED_SHORT, sizeof(PointGL), static_cast<int8_t*>(nullptr) + offsetof(PointGL, r));

	gl(EnableVertexAttribArray, funcs)(2);
	gl(VertexAttribIPointer, funcs)(2, 1, GL_SHORT, sizeof(PointGL), static_cast<int8_t*>(nullptr) + offsetof(PointGL, m));

	pathBuffers.emplace_back(pbItem);
	return pathBuffers.size() - 1;
}
void ViewerPriv::freePbi(int i) {
	pathBuffers[i].prev_unused = pathBuffers[0].prev_unused;
	pathBuffers[0].prev_unused = i;
}

void ViewerPriv::zoomInOut(float d) {
	if (slice_mode) {
		d = d / radU;
		if (d>0 && d<1) d = 1;
		if (d<0 && d>-1) d = -1;
		slice_delta += d;
		if (slice_delta<-slice_num) slice_delta = -slice_num;
		if (slice_delta>slice_num) slice_delta = slice_num;
	}
	else {
		radU = radU*pow(0.96, d);
		if (radU>2 * max_dimension_size) {
			radU = 2 * max_dimension_size;
		}
		else if (radU<5 * min_voxel_size) {
			radU = 5 * min_voxel_size;
		}
		updateMVPMatrices();
	}
	viewer->update();
}

void ViewerPriv::resizeFrameBuffers(int ww, int hh) {
	int width, height;
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		width = ww*(1 - scale_factorTemp);
		height = hh*(1 - scale_factorTemp);
	}
	else {
		width = (ww + scale_factorTemp) / (1 + scale_factorTemp);
		height = (hh + scale_factorTemp) / (1 + scale_factorTemp);
	}

	if (width != fbo_width || height != fbo_height) {
		fbo_width = width;
		fbo_height = height;

		//gl(ActiveTexture, funcs)(GL_TEXTURE0);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_scale[0]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_scale[1]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);

		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_opaque[0]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_opaque[1]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);

		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_surface[0]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_surface[1]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);

		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[0]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, fbo_width, fbo_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[1]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_R32I, fbo_width, fbo_height, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[2]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_R32I, fbo_width, fbo_height, 0, GL_RED_INTEGER, GL_INT, nullptr);
		gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_edges[3]);
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);

		for (int i = 0; i<3; i++) {
			gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_cubes[2 * i + 0]);
			gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_DEPTH_STENCIL, fbo_width, fbo_height, 0, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8, nullptr);
			gl(BindTexture, funcs)(GL_TEXTURE_2D, fbotex_cubes[2 * i + 1]);
			gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA8, fbo_width, fbo_height, 0, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, nullptr);
		}
		updateMVPMatrices();
	}
}

void ViewerPriv::paintEdge() {
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_edges);
	gl(Viewport, funcs)(0, 0, fbo_width, fbo_height);
	gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	if (!show_edges)
		return;

	float umpp;
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		umpp = radU*(1 - scale_factorTemp) / fbo_width;
	}
	else {
		umpp = radU / fbo_width;
	}

	gl(Disable, funcs)(GL_BLEND);

	viewerShared->progs[P_EDGE]->bind();
	auto locp = viewerShared->progs[P_EDGE]->uniformLocation("p0int");
	auto& p = curPos.point;
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	viewerShared->progs[P_EDGE]->setUniformValue("mView", mView);
	viewerShared->progs[P_EDGE]->setUniformValue("mProj", mProj);
	auto idx_loc = viewerShared->progs[P_EDGE]->uniformLocation("idx");
	if (num_path) {
		viewerShared->progs[P_EDGE]->setUniformValue("umpp", umpp);
		viewerShared->progs[P_EDGE]->setUniformValue("color_edge[0]", colors[C_ATTENTION]);
		gl(Uniform1ui, funcs)(idx_loc, static_cast<GLuint>(SIZE_MAX));
		gl(BindVertexArray, funcs)(pathBuffers[pbiPath].vao);
		gl(DrawArrays, funcs)(GL_LINE_STRIP, 0, num_path);
	}


	Tree curTree{};
	if (curPos.edge && curPos.edge.tree())
		curTree = curPos.edge.tree();
	for (auto e : graph.edges()) {
		QColor color = colors[C_TYPE_0]; //unselect neuron
		switch (e.type())
		{
		case 0:
			color = colors[C_TYPE_0];
			break;
		case 1:
			color = colors[C_TYPE_1];
			break;
		case 2:
			color = colors[C_TYPE_4];
			break;
		case 3:
			color = colors[C_TYPE_2];
			break;
		case 4:
			color = colors[C_TYPE_3];
			break;
		default:
			break;
		}

		if (checkedMode) {
			if (e.checked() == 1)
				color = colors[C_TYPE_6];

			else if (e.checked() == 2)
			{
				color = colors[C_TYPE_7];
			}
		}
		if (e.inLoop() || !e.tree()) {
			color = colors[C_ATTENTION];
		}
		else if (e.tree() && e.tree() == curTree) {
			//color = colors[C_TYPE_0]; //select neuron
			color =color.lighter(150);
		}


		if (e.tree() && e.tree().selected()) {
			viewerShared->progs[P_EDGE]->setUniformValue("umpp", 2 * umpp);
		}
		else {
			viewerShared->progs[P_EDGE]->setUniformValue("umpp", umpp);
		}

		if (curPos.edge && curPos.edge == e) {
			if (tgtPos.edge && tgtPos.edge == e) {
				auto ep = EdgePriv::get(e);
				gl(Uniform1ui, funcs)(idx_loc, static_cast<GLuint>(ep->index + 1));
				gl(BindVertexArray, funcs)(pathBuffers[ep->vaoi].vao);
				auto a = curPos.index;
				auto b = tgtPos.index;
				if (a>b)
					std::swap(a, b);
				if (b - a>0) {
					viewerShared->progs[P_EDGE]->setUniformValue("color_edge[0]", color);
					gl(DrawArrays, funcs)(GL_LINE_STRIP, a, b - a + 1);

				}
				if (a>0 || ep->points.size() - b>1) {
					viewerShared->progs[P_EDGE]->setUniformValue("color_edge[0]", color.darker(180));
					if (a>0) {
						gl(DrawArrays, funcs)(GL_LINE_STRIP, 0, a + 1);
					}
					if (ep->points.size() - b>1) {
						gl(DrawArrays, funcs)(GL_LINE_STRIP, b, ep->points.size() - b);
					}
				}
				continue;
			}
		}
		else {
			if (!tgtPos.edge || tgtPos.edge != e) {
				color = color.darker(180);
			}
		}

		auto ep = EdgePriv::get(e);
		gl(Uniform1ui, funcs)(idx_loc, static_cast<GLuint>(ep->index + 1));
		viewerShared->progs[P_EDGE]->setUniformValue("color_edge[0]", color);
		gl(BindVertexArray, funcs)(pathBuffers[ep->vaoi].vao);
		gl(DrawArrays, funcs)(GL_LINE_STRIP, 0, ep->points.size());
	}

	gl(BindVertexArray, funcs)(vao_fixed);
	viewerShared->progs[P_VERT]->bind();
	viewerShared->progs[P_VERT]->setUniformValue("mView", mView);
	viewerShared->progs[P_VERT]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_VERT]->setUniformValue("umpp", umpp);
	viewerShared->progs[P_VERT]->bind();
	locp = viewerShared->progs[P_VERT]->uniformLocation("p0int");
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	auto cent_loc = viewerShared->progs[P_VERT]->uniformLocation("center");
	idx_loc = viewerShared->progs[P_VERT]->uniformLocation("idx");
	auto nid_loc = viewerShared->progs[P_VERT]->uniformLocation("nid");

	for (auto v : graph.vertices()) {
		bool darker = false;
		if (curPos.edge && (curPos.edge.leftVertex() == v || curPos.edge.rightVertex() == v)) {
			if (tgtPos.edge && (tgtPos.edge.leftVertex() == v || tgtPos.edge.rightVertex() == v)) {
				if (curPos.edge == tgtPos.edge) {
					//darker=true;
				}
				else {
					darker = true;
				}
			}
		}
		else {
			if (!tgtPos.edge || (tgtPos.edge.leftVertex() != v && tgtPos.edge.rightVertex() != v)) {
				darker = true;
			}
		}
		//
		QColor color;
		if (v.inLoop() || !v.finished()) {
			color = colors[C_ATTENTION];
		}
		else if (v.tree() && v.tree() == curTree) {
			color = colors[C_TYPE_2];
			if (darker)
				color = color.darker(80);
		}
		else {
			color = colors[C_TYPE_0];
			if (darker)
				color = color.darker(80);
		}
		if (v.checked()&& checkedMode)
		{
			color = colors[C_TYPE_OTHER];
			if (darker)
				color = color.darker(80);
		}
		int rad = 7;
		if (v.tree() && v.tree().root() == v) {
			rad = 12;
		}
		else if (v.neighbors().size()>1) {
			rad = 6;
		}

		viewerShared->progs[P_VERT]->setUniformValue("umpp", umpp);
		viewerShared->progs[P_VERT]->setUniformValue("color", color);
		gl(Uniform1ui, funcs)(idx_loc, GLuint(v.neighbors()[0].first.index() + 1));
		gl(Uniform1i, funcs)(nid_loc, GLint(v.neighbors()[0].second ? v.neighbors()[0].first.points().size() - 1 : 0));
		auto& p = v.point();
		gl(Uniform4i, funcs)(cent_loc, p._x, p._y, p._z, rad);
		gl(DrawArrays, funcs)(GL_TRIANGLE_FAN, 100, 34);
		//if(v.tree() && v.tree().selected()) {
		//gl(DrawArrays, funcs)(GL_LINES, 44, 16);
		//}
	}


	gl(Enable, funcs)(GL_BLEND);
}
void ViewerPriv::paintOpaque() {
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_opaque);
	gl(Viewport, funcs)(0, 0, fbo_width, fbo_height);
	gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	if (!show_edges)
		return;
	float umpp;
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		umpp = radU*(1 - scale_factorTemp) / fbo_width;
	}
	else {
		umpp = radU / fbo_width;
	}


	viewerShared->progs[P_LINE]->bind();
	viewerShared->progs[P_LINE]->setUniformValue("mView", mView);
	viewerShared->progs[P_LINE]->setUniformValue("mProj", mProj);
	auto locp = viewerShared->progs[P_LINE]->uniformLocation("p0int");
	viewerShared->progs[P_LINE]->setUniformValue("umpp", umpp);
	auto& p = curPos.point;
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	gl(BindVertexArray, funcs)(pathBuffers[pbiFixed].vao);
	viewerShared->progs[P_LINE]->setUniformValue("color", colors[C_AXIS]);
	for (int i = 0; i<9; i++) {
		if (cubes[i].data) {
			// if shown?
			gl(DrawArrays, funcs)(GL_LINES, 2 + i * 24, 24);
		}
	}

	viewerShared->progs[P_LINE]->setUniformValue("umpp", umpp);
	if (!pickA.isNull() && !pickB.isNull()) {
		gl(PointSize, funcs)(3);
		//funcs->glDisable(GL_LINE_SMOOTH);
		viewerShared->progs[P_LINE]->setUniformValue("color", colors[C_ATTENTION]);
		gl(DrawArrays, funcs)(GL_LINES, 0, 2);
	}

	viewerShared->progs[P_POINT]->bind();
	viewerShared->progs[P_POINT]->setUniformValue("mView", mView);
	viewerShared->progs[P_POINT]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_POINT]->setUniformValue("umpp", umpp / 2);
	locp = viewerShared->progs[P_POINT]->uniformLocation("p0int");
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	if (num_progr) {
		// dots showing computing progress
		gl(BindVertexArray, funcs)(vao_progr);
		viewerShared->progs[P_POINT]->setUniformValue("color", colors[C_ATTENTION]);
		gl(PointSize, funcs)(1);
		gl(DrawArrays, funcs)(GL_POINTS, 0, num_progr);
	}

	gl(BindVertexArray, funcs)(vao_fixed);
	// axis labels
	viewerShared->progs[P_MARK]->bind();
	viewerShared->progs[P_MARK]->setUniformValue("mView", mView);
	viewerShared->progs[P_MARK]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_MARK]->setUniformValue("umpp", umpp);
	viewerShared->progs[P_MARK]->bind();
	locp = viewerShared->progs[P_MARK]->uniformLocation("p0int");
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	auto cent_loc = viewerShared->progs[P_MARK]->uniformLocation("center");

	viewerShared->progs[P_MARK]->setUniformValue("umpp", umpp);
	//std::cout << colors[C_AXIS].red() <<"\t"<< colors[C_AXIS].green() << "\t" << colors[C_AXIS].blue() << "\t" << std::endl;
	viewerShared->progs[P_MARK]->setUniformValue("color", colors[C_AXIS]);
	for (int i = 0; i<9; i++) {
		if (cubes[i].data) {
			double x0 = cubes[i].xform.origin[0] + cubes[i].xform.direction[0 + 0 * 3] * cubes[i].data.cubeId().x0 + cubes[i].xform.direction[0 + 1 * 3] * cubes[i].data.cubeId().y0 + cubes[i].xform.direction[0 + 2 * 3] * cubes[i].data.cubeId().z0;
			double y0 = cubes[i].xform.origin[1] + cubes[i].xform.direction[1 + 0 * 3] * cubes[i].data.cubeId().x0 + cubes[i].xform.direction[1 + 1 * 3] * cubes[i].data.cubeId().y0 + cubes[i].xform.direction[1 + 2 * 3] * cubes[i].data.cubeId().z0;
			double z0 = cubes[i].xform.origin[2] + cubes[i].xform.direction[2 + 0 * 3] * cubes[i].data.cubeId().x0 + cubes[i].xform.direction[2 + 1 * 3] * cubes[i].data.cubeId().y0 + cubes[i].xform.direction[2 + 2 * 3] * cubes[i].data.cubeId().z0;
			auto dx = cubes[i].data.width();
			auto dy = cubes[i].data.height();
			auto dz = cubes[i].data.depth();
			{//x
				int dir = 0;
				Point p{ x0 + cubes[i].xform.direction[0 + dir * 3] * dx,y0 + cubes[i].xform.direction[1 + dir * 3] * dx,z0 + cubes[i].xform.direction[2 + dir * 3] * dx };
				gl(Uniform4i, funcs)(cent_loc, p._x, p._y, p._z, 8);
				gl(DrawArrays, funcs)(GL_LINES, 18, 4);
			}
			{//y
				int dir = 1;
				Point p{ x0 + cubes[i].xform.direction[0 + dir * 3] * dy,y0 + cubes[i].xform.direction[1 + dir * 3] * dy,z0 + cubes[i].xform.direction[2 + dir * 3] * dy };
				gl(Uniform4i, funcs)(cent_loc, p._x, p._y, p._z, 8);
				gl(DrawArrays, funcs)(GL_LINES, 22, 4);
			}
			{//z
				int dir = 2;
				Point p{ x0 + cubes[i].xform.direction[0 + dir * 3] * dz,y0 + cubes[i].xform.direction[1 + dir * 3] * dz,z0 + cubes[i].xform.direction[2 + dir * 3] * dz };
				gl(Uniform4i, funcs)(cent_loc, p._x, p._y, p._z, 8);
				gl(DrawArrays, funcs)(GL_LINES, 26, 6);
			}
		}
	}

	//gl(Disable,funcs)(GL_DEPTH_TEST);

	if (tgtPos.point.valid()) {
		viewerShared->progs[P_MARK]->setUniformValue("color", colors[C_ATTENTION]);
		gl(Uniform4i, funcs)(cent_loc, tgtPos.point._x, tgtPos.point._y, tgtPos.point._z, 15);
		if (tgtPos.edge) {
			gl(DrawArrays, funcs)(GL_LINES, 10, 8); //open cross
		}
		else {
			gl(DrawArrays, funcs)(GL_LINES, 6, 4); // cross
		}
		viewerShared->progs[P_MARK]->setUniformValue("umpp", float(umpp * 1.5));
		viewerShared->progs[P_MARK]->setUniformValue("color", colors[C_AXIS]);

		gl(Uniform4i, funcs)(cent_loc, tgtPos.point._x, tgtPos.point._y, tgtPos.point._z, 15);
		if (tgtPos.edge) {
			gl(DrawArrays, funcs)(GL_LINES, 10, 8); //open cross
		}
		else {
			gl(DrawArrays, funcs)(GL_LINES, 6, 4); // cross
		}
	}
	if (curPos.point.valid()) {
		viewerShared->progs[P_MARK]->setUniformValue("umpp", float(umpp));
		viewerShared->progs[P_MARK]->setUniformValue("color", colors[C_CHAN0]);
		gl(Uniform4i, funcs)(cent_loc, curPos.point._x, curPos.point._y, curPos.point._z, 11);
		gl(DrawArrays, funcs)(GL_LINE_LOOP, 101, 32);
		// Neurons, in circle, mark
		viewerShared->progs[P_MARK]->setUniformValue("umpp", float(umpp * 1.5));
		viewerShared->progs[P_MARK]->setUniformValue("color", colors[C_AXIS]);
		gl(Uniform4i, funcs)(cent_loc, curPos.point._x, curPos.point._y, curPos.point._z, 11);
		gl(DrawArrays, funcs)(GL_LINE_LOOP, 101, 32);

	}
	//gl(Enable, funcs)(GL_DEPTH_TEST);

	viewerShared->progs[P_SHADE]->bind();
	viewerShared->progs[P_SHADE]->setUniformValue("mView", mView);
	viewerShared->progs[P_SHADE]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_SHADE]->setUniformValue("umpp", 2 * umpp);
	locp = viewerShared->progs[P_SHADE]->uniformLocation("p0int");
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);

	viewerShared->progs[P_NODE]->bind();
	locp = viewerShared->progs[P_NODE]->uniformLocation("p0int");
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);
	viewerShared->progs[P_NODE]->setUniformValue("mView", mView);
	viewerShared->progs[P_NODE]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_NODE]->setUniformValue("color[0]", colors[C_TYPE_4]);
	viewerShared->progs[P_NODE]->setUniformValue("color[1]", colors[C_TYPE_5]);
	viewerShared->progs[P_NODE]->setUniformValue("color[2]", colors[C_TYPE_6]);
	viewerShared->progs[P_NODE]->setUniformValue("color[3]", colors[C_TYPE_7]);
	viewerShared->progs[P_NODE]->setUniformValue("umpp", 10 * umpp);
	//gl(Disable,funcs)(GL_DEPTH_TEST);
	for (auto e : graph.edges()) {
		auto ep = EdgePriv::get(e);
		gl(BindVertexArray, funcs)(pathBuffers[ep->vaoi].vao);
		gl(DrawArrays, funcs)(GL_POINTS, 0, e.points().size());
	}
	//gl(Enable, funcs)(GL_DEPTH_TEST);

	/*
	for(auto e: graph.edges()) {
	size_t a=0;
	size_t b=0;
	bool red=false;
	//
	if(e.inLoop()) {
	a=0;
	b=e.points().size();
	red=true;
	}
	if(e==tgtPos.edge) {
	if(e==curPos.edge) {
	a=curPos.index;
	b=tgtPos.index-a+1;
	if(tgtPos.index<a) {
	a=tgtPos.index;
	b=curPos.index-a+1;
	}
	} else {
	a=0;
	b=e.points().size();
	}
	}
	if(b>0) {
	auto ep=EdgePriv::get(e);

	if(red) {
	viewerShared->progs[P_SHADE]->setUniformValue("color_shade", colors[C_ALERT]);
	} else {
	viewerShared->progs[P_SHADE]->setUniformValue("color_shade", colors[C_ATTENTION]);
	}
	gl(BindVertexArray, funcs)(pathBuffers[ep->vaoi].vao);
	gl(DrawArrays, funcs)(GL_LINE_STRIP, a, b);
	}
	}
	*/
}
void ViewerPriv::paintSurface() {
	gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_surface);
	gl(Viewport, funcs)(0, 0, fbo_width, fbo_height);
	gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	gl(Disable, funcs)(GL_BLEND);

	QColor color{ 255, 255, 255, 120 };
	viewerShared->progs[P_MESH]->bind();
	viewerShared->progs[P_MESH]->setUniformValue("mView", mView);
	viewerShared->progs[P_MESH]->setUniformValue("mProj", mProj);
	viewerShared->progs[P_MESH]->setUniformValue("color", color);
	auto locp = viewerShared->progs[P_MESH]->uniformLocation("p0int");
	auto& p = curPos.point;
	gl(Uniform3i, funcs)(locp, p._x, p._y, p._z);

	gl(BindVertexArray, funcs)(vao_mesh);
	gl(PolygonMode, funcs)(GL_FRONT_AND_BACK, GL_FILL);


	gl(BindBuffer, funcs)(GL_ELEMENT_ARRAY_BUFFER, vbo_mesh[1]);
	gl(DrawElements, funcs)(GL_TRIANGLES, meshIdxes.size(), GL_UNSIGNED_INT, nullptr);
	gl(Enable, funcs)(GL_BLEND);
}
void ViewerPriv::paintVolume() {
	gl(Enable, funcs)(GL_STENCIL_TEST);
	gl(StencilFunc, funcs)(GL_NOTEQUAL, 1, 0xff);
	gl(StencilOp, funcs)(GL_KEEP, GL_KEEP, GL_REPLACE);
	gl(ActiveTexture, funcs)(GL_TEXTURE1);

	viewerShared->progs[P_VOLUME]->bind();
	viewerShared->progs[P_VOLUME]->setUniformValue("mrView", mrView);
	viewerShared->progs[P_VOLUME]->setUniformValue("mrProj", mrProj);
	gl(BindVertexArray, funcs)(vao_fixed);

	for (int ch = 0; ch<3; ch++) {
		gl(BindFramebuffer, funcs)(GL_FRAMEBUFFER, fbo_cubes[ch]);
		gl(Viewport, funcs)(0, 0, fbo_width, fbo_height);
		gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
		viewerShared->progs[P_VOLUME]->setUniformValue("color_volume", colors[C_CHAN0 + ch]);

		for (int layer = 0; layer<3; layer++) {
			if (cube_textures[ch * 3 + layer]) {
				gl(BindTexture, funcs)(GL_TEXTURE_3D, viewerShared->cubeTextures[cube_textures[ch * 3 + layer]].tex);
				QVector2D xfunc(xfuncs[ch * 3 + layer].first, xfuncs[ch * 3 + layer].second);
				viewerShared->progs[P_VOLUME]->setUniformValue("xfunc_cube", xfunc);
				viewerShared->progs[P_VOLUME]->setUniformValue("mrTex", cube_matrices[ch * 3 + layer]);
				auto mvs = min_voxel_sizes[ch * 3 + layer];
				if (slice_mode) {
					viewerShared->progs[P_VOLUME]->setUniformValue("zparsCube", QVector3D(
						-1.0*slice_delta / slice_num,
						-1.0*slice_delta / slice_num,
						qMax(1.0 / slice_num, 9 * mvs / radU / 8)));
				}
				else {
					viewerShared->progs[P_VOLUME]->setUniformValue("zparsCube", QVector3D(
						-1.0*slice_shown / slice_num,
						1.0*slice_shown / slice_num,
						qMax(1.0 / slice_num, 9 * mvs / radU / 8)));
				}
				gl(DrawArrays, funcs)(GL_TRIANGLE_STRIP, 0, 4);
			}
		}
	}
	gl(Disable, funcs)(GL_STENCIL_TEST);
}
void ViewerPriv::paintRuler()
{
		gl(BindVertexArray, funcs)(vao_fixed);
		viewerShared->progs[P_LINE2]->bind();
		viewerShared->progs[P_LINE2]->setUniformValue("mView", mProj);
		auto locp = viewerShared->progs[P_LINE2]->uniformLocation("p0int");
		viewerShared->progs[P_LINE2]->setUniformValue("s", rulerlength);

		auto& p = curPos.point;
		gl(Uniform3i, funcs)(locp, 0, 0, 0);
		viewerShared->progs[P_LINE2]->setUniformValue("color", colors[C_AXIS]);
		gl(DrawArrays, funcs)(GL_LINES, 60, 24);

}

void ViewerPriv::paintText(QImage* textImage, bool update)
{
	gl(BindTexture, funcs)(GL_TEXTURE_2D, textTexture);

	if (update) {
		gl(TexImage2D, funcs)(GL_TEXTURE_2D, 0, GL_RGBA, textImage->width(), textImage->height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, textImage->bits());
		gl(GenerateMipmap, funcs)(GL_TEXTURE_2D);
	}
	gl(BindVertexArray, funcs)(vao_fixed);
	viewerShared->progs[P_TEXT]->bind();
	QMatrix4x4 proj;
	int range = fbo_width;
	float rad = fbo_width/ float(fbo_height);

	proj.ortho(-fbo_width / 2, fbo_width / 2, -fbo_height / 2, fbo_height / 2, 0.1, 1000);
	viewerShared->progs[P_TEXT]->setUniformValue("mView", proj);
	auto locp = viewerShared->progs[P_TEXT]->uniformLocation("p0int");
	viewerShared->progs[P_TEXT]->setUniformValue("s", rulerlength);
	float vspan = fbo_height / fbo_width;
	viewerShared->progs[P_TEXT]->setUniformValue("w", float(range));
	viewerShared->progs[P_TEXT]->setUniformValue("rad", float(rad));

	auto& p = curPos.point;
	gl(Uniform3i, funcs)(locp, 0, 0, 0);
	viewerShared->progs[P_TEXT]->setUniformValue("color", colors[C_AXIS]);
	gl(DrawArrays, funcs)(GL_TRIANGLE_STRIP, 0, 4);
}

void ViewerPriv::paintBlank() {
	int width, height;
	int scale_factorTemp = 0;
	if (scale_factorTemp <0) {
		width = fbo_width / (1 - scale_factorTemp);
		height = fbo_height / (1 - scale_factorTemp);
	}
	else {
		width = fbo_width*(1 + scale_factorTemp);
		height = fbo_height*(1 + scale_factorTemp);
	}
	gl(Viewport, funcs)(0, 0, width, height);
	gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
}
void ViewerPriv::paintSorted(GLuint fbo) {
	gl(BindFramebuffer, funcs)(GL_DRAW_FRAMEBUFFER, fbo);
	gl(Viewport, funcs)(0, 0, fbo_width, fbo_height);
	gl(Clear, funcs)(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	gl(BindVertexArray, funcs)(vao_fixed);
	viewerShared->progs[P_SORT]->bind();
	gl(DrawArrays, funcs)(GL_TRIANGLE_STRIP, 0, 4);
}

void ViewerPriv::paintFinish(GLuint fbo) {
	bool scale = scale_factor != 0;
	paintSorted(scale ? fbo_scale : fbo);
	if (scale) {
		gl(BindFramebuffer, funcs)(GL_READ_FRAMEBUFFER, fbo_scale);
		gl(BindFramebuffer, funcs)(GL_DRAW_FRAMEBUFFER, fbo);
		int width, height;
		int scale_factorTemp = 0;
		if (scale_factorTemp <0) {
			width = fbo_width / (1 - scale_factorTemp);
			height = fbo_height / (1 - scale_factorTemp);
		}
		else {
			width = fbo_width*(1 + scale_factorTemp);
			height = fbo_height*(1 + scale_factorTemp);
		}
		gl(Viewport, funcs)(0, 0, width, height);
		gl(BlitFramebuffer, funcs)(0, 0, fbo_width, fbo_height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_LINEAR);
	}
}

void ViewerPriv::loadSurface(const QString& fn) {
	QFile f{ fn };
	if (!f.open(QIODevice::ReadOnly))
		throwError("Failed to open");
	QTextStream fs{ &f };

	meshVerts.clear();
	meshIdxes.clear();
	QRegExp regex{ "\\s+" };
	while (true) {
		auto line = fs.readLine();
		if (line.isNull()) {
			if (fs.atEnd())
				break;
			throwError("Failed to read line");
		}
		if (line.isEmpty())
			continue;
		if (line[0] == '#')
			continue;
		if (line.startsWith("mtllib "))
			continue;
		if (line.startsWith("o "))
			continue;
		if (line.startsWith("vn "))
			continue;
		if (line.startsWith("usemtl "))
			continue;
		if (line.startsWith("s "))
			continue;
		if (line.startsWith("v ")) {
			auto verts = line.mid(2).split(regex, QString::SkipEmptyParts);
			if (verts.size() != 3)
				throwError("Wrong number of coords: ", line.toStdString());
			double v[3];
			for (int i = 0; i<3; i++) {
				bool ok;
				v[i] = verts[i].toDouble(&ok);
				if (!ok)
					throwError("Cannot get double: ", line.toStdString());
			}
			Point p{ v[0], v[1], v[2] };
			meshVerts.push_back(MeshVert{ p._x, p._y, p._z, 0.0, 0.0, 0.0 });
		}
		else if (line.startsWith("f ")) {
			auto verts = line.mid(2).split(regex, QString::SkipEmptyParts);
			if (verts.size() != 3)
				throwError("Wrong number of coords: ", line.toStdString());
			for (int i = 0; i<3; i++) {
				auto vinfos = verts[i].split("//", QString::SkipEmptyParts);
				if (vinfos.size() <= 0)
					throwError("No vert index: ", line.toStdString());
				bool ok;
				auto vi = vinfos[0].toUInt(&ok);
				if (!ok)
					throwError("Cannot get uint: ", line.toStdString());
				meshIdxes.push_back(vi - 1);
			}
		}
		else {
			throwError("Unknown line: ", line.toStdString());
		}
	}
	printMessage("nv: ", meshVerts.size(), "; ni: ", meshIdxes.size());
	if (!meshVerts.empty() && !meshIdxes.empty()) {

		std::vector<QVector3D> vertNormals(meshVerts.size(), { 0.0f, 0.0f, 0.0f });
		for (size_t i = 0; i + 2<meshIdxes.size(); i += 3) {
			auto a = meshIdxes[i];
			auto b = meshIdxes[i + 1];
			auto c = meshIdxes[i + 2];
			QVector3D u(meshVerts[b].x - meshVerts[a].x, meshVerts[b].y - meshVerts[a].y, meshVerts[b].z - meshVerts[a].z);
			QVector3D v(meshVerts[c].x - meshVerts[a].x, meshVerts[c].y - meshVerts[a].y, meshVerts[c].z - meshVerts[a].z);
			QVector3D w(meshVerts[c].x - meshVerts[b].x, meshVerts[c].y - meshVerts[b].y, meshVerts[c].z - meshVerts[b].z);

			auto na = QVector3D::crossProduct(v, u);
			if (QVector3D::dotProduct(na, vertNormals[a]) >= 0) {
				vertNormals[a] += na;
			}
			else {
				vertNormals[a] -= na;
			}
			auto nb = QVector3D::crossProduct(-u, w);
			if (QVector3D::dotProduct(nb, vertNormals[b]) >= 0) {
				vertNormals[b] += nb;
			}
			else {
				vertNormals[b] -= nb;
			}
			auto nc = QVector3D::crossProduct(w, v);
			if (QVector3D::dotProduct(nc, vertNormals[c]) >= 0) {
				vertNormals[c] += nc;
			}
			else {
				vertNormals[c] -= nc;
			}
		}
		for (size_t i = 0; i<meshVerts.size(); i++) {
			auto n = vertNormals[i].length();
			meshVerts[i].nx = vertNormals[i].x() / n;
			meshVerts[i].ny = vertNormals[i].y() / n;
			meshVerts[i].nz = vertNormals[i].z() / n;
		}
		//
		//
		//
		//

		viewer->makeCurrent();
		gl(BindBuffer, funcs)(GL_ARRAY_BUFFER, vbo_mesh[0]);
		gl(BufferData, funcs)(GL_ARRAY_BUFFER, sizeof(MeshVert)*meshVerts.size(), &meshVerts[0], GL_STATIC_DRAW);
		gl(BindBuffer, funcs)(GL_ELEMENT_ARRAY_BUFFER, vbo_mesh[1]);
		gl(BufferData, funcs)(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint)*meshIdxes.size(), &meshIdxes[0], GL_STATIC_DRAW);
		viewer->doneCurrent();
	}
}
void ViewerPriv::loadColormap(const QString& fn) {
	QFile f{ fn };
	if (!f.open(QIODevice::ReadOnly))
		throwError("Failed to open");
	QTextStream fs{ &f };
	std::vector<QColor> cols;
	while (true) {
		auto line = fs.readLine();
		if (line.isNull()) {
			if (fs.atEnd())
				break;
			throwError("Failed to read line");
		}
		if (line.isEmpty())
			continue;
		//if(line[0]=='#')
		//continue;
		QColor col{ line };
		if (!col.isValid())
			throwError("Error color: ", line.toStdString());
		cols.push_back(col);
	}
	std::swap(colormap, cols);
}
void ViewerPriv::rotationTimeout() {
	QVector3D a(0, 0, -1);
	QVector3D b(.02, 0, -1);
	a = (mrView*mrProj*a);
	a.normalize();
	b = (mrView*mrProj*b);
	b.normalize();
	QVector3D norm = QVector3D::crossProduct(a, b);
	QMatrix4x4 mat{};
	float proj = QVector3D::dotProduct(a, b);
	if (proj<-1) proj = -1;
	if (proj>1) proj = 1;
	float r = 360 * acos(proj) / 2 / M_PI;
	mat.rotate(-r, norm);
	up = mat*up;
	rgt = mat*rgt;
	updateMVPMatrices();
	viewer->update();
}
void ViewerPriv::toggleRotate() {
	if (timer->isActive()) {
		timer->stop();
	}
	else {
		timer->start();
	}
}